using System;
using SunflowSharp.Core;
using SunflowSharp.Maths;

namespace SunflowSharp.Core.Primitive
{
    public class BanchoffSurface : PrimitiveList
    {
        public bool update(ParameterList pl, SunflowAPI api)
        {
            return true;
        }

        public BoundingBox getWorldBounds(Matrix4 o2w)
        {
            BoundingBox bounds = new BoundingBox(1.5f);
            if (o2w != null)
                bounds = o2w.transform(bounds);
            return bounds;
        }

        public float getPrimitiveBound(int primID, int i)
        {
            return (i & 1) == 0 ? -1.5f : 1.5f;
        }

        public int getNumPrimitives()
        {
            return 1;
        }

        public void prepareShadingState(ShadingState state)
        {
            state.init();
            state.getRay().getPoint(state.getPoint());
            Instance parent = state.getInstance();
            Point3 n = parent.transformWorldToObject(state.getPoint());
            state.getNormal().set(n.x * (2 * n.x * n.x - 1), n.y * (2 * n.y * n.y - 1), n.z * (2 * n.z * n.z - 1));
            state.getNormal().normalize();
            state.setShader(parent.getShader(0));
            state.setModifier(parent.getModifier(0));
            // into world space
            Vector3 worldNormal = parent.transformNormalObjectToWorld(state.getNormal());
            state.getNormal().set(worldNormal);
            state.getNormal().normalize();
            state.getGeoNormal().set(state.getNormal());
            // create basis in world space
            state.setBasis(OrthoNormalBasis.makeFromW(state.getNormal()));
        }

        public void intersectPrimitive(Ray r, int primID, IntersectionState state)
        {
            // intersect in local space
            float rd2x = r.dx * r.dx;
            float rd2y = r.dy * r.dy;
            float rd2z = r.dz * r.dz;
            float ro2x = r.ox * r.ox;
            float ro2y = r.oy * r.oy;
            float ro2z = r.oz * r.oz;
            // setup the quartic coefficients
            // some common terms could probably be shared across these
            double A = (rd2y * rd2y + rd2z * rd2z + rd2x * rd2x);
            double B = 4 * (r.oy * rd2y * r.dy + r.oz * r.dz * rd2z + r.ox * r.dx * rd2x);
            double C = (-rd2x - rd2y - rd2z + 6 * (ro2y * rd2y + ro2z * rd2z + ro2x * rd2x));
            double D = 2 * (2 * ro2z * r.oz * r.dz - r.oz * r.dz + 2 * ro2x * r.ox * r.dx + 2 * ro2y * r.oy * r.dy - r.ox * r.dx - r.oy * r.dy);
            double E = 3.0f / 8.0f + (-ro2z + ro2z * ro2z - ro2y + ro2y * ro2y - ro2x + ro2x * ro2x);
            // solve equation
            double[] t = Solvers.solveQuartic(A, B, C, D, E);
            if (t != null)
            {
                // early rejection
                if (t[0] >= r.getMax() || t[t.Length - 1] <= r.getMin())
                    return;
                // find first intersection in front of the ray
                for (int i = 0; i < t.Length; i++)
                {
                    if (t[i] > r.getMin())
                    {
                        r.setMax((float)t[i]);
                        state.setIntersection(0, 0, 0);
                        return;
                    }
                }
            }
        }

        public PrimitiveList getBakingPrimitives()
        {
            return null;
        }
    }
}